Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ

Node / meshtastic / Meshtastic-Android / files / core / network / src / commonTest / kotlin / org / meshtastic / core / network / radio / BleRadioTransportReconnectCrashTest.kt

Displaying Raw β€’ Download

core/network/src/commonTest/kotlin/org/meshtastic/core/network/radio/BleRadioTransportReconnectCrashTest.kt renovate/net.java.dev.jna-jna-5.x (5a8f4966) Text, 15.11 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.network.radio

Tff7b72import T7ee787dev.mokkery.MockMode
Tff7b72import T7ee787dev.mokkery.answering.returns
Tff7b72import T7ee787dev.mokkery.every
Tff7b72import T7ee787dev.mokkery.matcher.any
Tff7b72import T7ee787dev.mokkery.mock
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.ExperimentalCoroutinesApi
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asStateFlow
Tff7b72import T7ee787kotlinx.coroutines.test.advanceTimeBy
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnection
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionState
Tff7b72import T7ee787org.meshtastic.core.ble.BleDevice
Tff7b72import T7ee787org.meshtastic.core.ble.BleService
Tff7b72import T7ee787org.meshtastic.core.ble.BleWriteType
Tff7b72import T7ee787org.meshtastic.core.ble.DisconnectReason
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleConnection
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleDevice
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleScanner
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBluetoothRepository
Tff7b72import T7ee787kotlin.test.BeforeTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72import T7ee787kotlin.time.Duration

T8b949e/**
* Tests covering the BLE reconnect crash fixes in [BleRadioTransport]:
* 1. **CancellationException / GATT 133 fix**: [discoverServicesAndSetupCharacteristics] previously had a bare `catch
* (e: Exception)` that silently swallowed [CancellationException], meaning [BleConnection.disconnect] was never
* called when the scope was cancelled. This leaked the underlying BluetoothGatt handle and caused GATT status 133 on
* every subsequent reconnect. The fix adds an explicit `if (e is CancellationException)` branch that calls
* [disconnect] under [NonCancellable] before re-throwing.
* 2. **close() calls disconnect**: Verifies that calling [BleRadioTransport.close] triggers [BleConnection.disconnect]
* exactly once so the GATT handle is always released.
* 3. **Reconnect after failure respects policy backoff**: After a configurable number of consecutive failures the
* transport signals a transient (non-permanent) disconnect to the callback.
*/
Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tff7b72class T56d364BleRadioTransportReconnectCrashTest Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3scanner Tff7b72= Te6edf3FakeBleScannerTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3bluetoothRepository Tff7b72= Te6edf3FakeBluetoothRepositoryTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3connection Tff7b72= Te6edf3FakeBleConnectionTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3connectionFactory Tff7b72= Te6edf3FakeBleConnectionFactoryTb4b4b4(Te6edf3connectionTb4b4b4)
Tff7b72private Tff7b72val Te6edf3service Tff7b72= Te6edf3mockTff7b72<Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3repositoryTb4b4b4.Te6edf3RadioInterfaceServiceTff7b72>Tb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ffAA:BB:CC:DD:EE:FFTa5d6ff"

Tf0883e@BeforeTest
Tff7b72fun Td2a8ffsetupTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3setHasPermissionsTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3setBluetoothEnabledTb4b4b4(Tff7b72trueTb4b4b4)
Tb4b4b4}

T8b949e// ─── close() triggers disconnect ─────────────────────────────────────────────────────────────

T8b949e/**
* After [BleRadioTransport.close], [FakeBleConnection.disconnect] must be called.
*
* This validates the primary invariant introduced by the fix: GATT cleanup (disconnect) always runs β€” even when the
* coroutine scope is cancelled β€” by wrapping the call in [NonCancellable].
*/
Tf0883e@Test
Tff7b72fun Td2a8ff`close calls disconnect to clean up GATT handle`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3device Tff7b72= Te6edf3FakeBleDeviceTb4b4b4(Te6edf3address Tff7b72= Te6edf3addressTb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffTest RadioTa5d6ff"Tb4b4b4)
Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3bondTb4b4b4(Te6edf3deviceTb4b4b4)

Tff7b72val Te6edf3bleTransport Tff7b72=
Te6edf3BleRadioTransportTb4b4b4(
Te6edf3scope Tff7b72= Tff7b72thisTb4b4b4,
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3bluetoothRepository Tff7b72= Te6edf3bluetoothRepositoryTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3connectionFactoryTb4b4b4,
Te6edf3callback Tff7b72= Te6edf3serviceTb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Tb4b4b4)
Te6edf3bleTransportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)

T8b949e// Allow the connection loop to reach the connected state.
Te6edf3advanceTimeByTb4b4b4(T79c0ff4Te6edf3_000LTb4b4b4)

Te6edf3bleTransportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)

T8b949e// disconnect() must be called: once by the connection loop teardown + once by close() itself.
T8b949e// We only assert it was called at least once β€” the exact count depends on timing.
Te6edf3assertTrueTb4b4b4(Te6edf3connectionTb4b4b4.Te6edf3disconnectCalls Tff7b72>Tff7b72= T79c0ff1Tb4b4b4, Ta5d6ff"Ta5d6ffExpected disconnect() to be called at least onceTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// ─── disconnect called on connection failure ──────────────────────────────────────────────────

T8b949e/**
* When [FakeBleConnection.connectAndAwait] always returns [BleConnectionState.Disconnected], the transport must
* still eventually call [BleConnection.disconnect] to ensure the GATT handle state machine is reset before the next
* attempt.
*
* Virtual-time budget: DEFAULT_FAILURE_THRESHOLD (3) Γ— (3 s settle + backoff) β‰ˆ 24 s.
*/
Tf0883e@Test
Tff7b72fun Td2a8ff`disconnect is called on connection failure`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3device Tff7b72= Te6edf3FakeBleDeviceTb4b4b4(Te6edf3address Tff7b72= Te6edf3addressTb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffTest RadioTa5d6ff"Tb4b4b4)
Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3bondTb4b4b4(Te6edf3deviceTb4b4b4)

T8b949e// Make every connection attempt fail.
Te6edf3connectionTb4b4b4.Te6edf3failNextN Tff7b72= Tffa657IntTb4b4b4.Te6edf3MAX_VALUE

Tff7b72val Te6edf3bleTransport Tff7b72=
Te6edf3BleRadioTransportTb4b4b4(
Te6edf3scope Tff7b72= Tff7b72thisTb4b4b4,
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3bluetoothRepository Tff7b72= Te6edf3bluetoothRepositoryTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3connectionFactoryTb4b4b4,
Te6edf3callback Tff7b72= Te6edf3serviceTb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Tb4b4b4)
Te6edf3bleTransportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)

Te6edf3advanceTimeByTb4b4b4(T79c0ff3T79c0ff0Te6edf3_000LTb4b4b4)

Te6edf3bleTransportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)

T8b949e// Each failed connectAndAwait round-trips through the reconnect loop; close() always disconnects.
Te6edf3assertTrueTb4b4b4(Te6edf3connectionTb4b4b4.Te6edf3disconnectCalls Tff7b72>Tff7b72= T79c0ff1Tb4b4b4, Ta5d6ff"Ta5d6ffdisconnect() not called after connection failureTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// ─── transient onDisconnect after failure threshold ──────────────────────────────────────────

T8b949e/**
* Mirrors [BleRadioTransportTest.`onDisconnect is called after DEFAULT_FAILURE_THRESHOLD consecutive failures`] but
* focuses specifically on the *reconnect* scenario introduced by the fix: after enough consecutive failures, the
* callback receives `isPermanent = false` β€” the transport keeps retrying rather than giving up permanently.
*
* Virtual time: 3 failures Γ— (3 s settle + backoff starting at 5 s) β‰ˆ 24 s.
*/
Tf0883e@Test
Tff7b72fun Td2a8ff`transient onDisconnect is signalled after failure threshold without giving up`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3device Tff7b72= Te6edf3FakeBleDeviceTb4b4b4(Te6edf3address Tff7b72= Te6edf3addressTb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffTest RadioTa5d6ff"Tb4b4b4)
Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3bondTb4b4b4(Te6edf3deviceTb4b4b4)

Te6edf3connectionTb4b4b4.Te6edf3connectException Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4.Te6edf3RadioNotConnectedExceptionTb4b4b4(Ta5d6ff"Ta5d6ffsimulated GATT failureTa5d6ff"Tb4b4b4)

Te6edf3every Tb4b4b4{ Te6edf3serviceTb4b4b4.Te6edf3onDisconnectTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tffa657Unit

Tff7b72val Te6edf3bleTransport Tff7b72=
Te6edf3BleRadioTransportTb4b4b4(
Te6edf3scope Tff7b72= Tff7b72thisTb4b4b4,
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3bluetoothRepository Tff7b72= Te6edf3bluetoothRepositoryTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3connectionFactoryTb4b4b4,
Te6edf3callback Tff7b72= Te6edf3serviceTb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Tb4b4b4)
Te6edf3bleTransportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)

Te6edf3advanceTimeByTb4b4b4(T79c0ff2T79c0ff4Te6edf3_001LTb4b4b4)

T8b949e// Transient disconnect must have been signalled.
Te6edf3devTb4b4b4.Te6edf3mokkeryTb4b4b4.Te6edf3verify Tb4b4b4{ Te6edf3serviceTb4b4b4.Te6edf3onDisconnectTb4b4b4(Te6edf3isPermanent Tff7b72= Tff7b72falseTb4b4b4, Te6edf3errorMessage Tff7b72= Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e// Permanent disconnect must NEVER be called by the transport on its own.
Te6edf3devTb4b4b4.Te6edf3mokkeryTb4b4b4.Te6edf3verifyTb4b4b4(Te6edf3mode Tff7b72= Te6edf3devTb4b4b4.Te6edf3mokkeryTb4b4b4.Te6edf3verifyTb4b4b4.Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{
Te6edf3serviceTb4b4b4.Te6edf3onDisconnectTb4b4b4(Te6edf3isPermanent Tff7b72= Tff7b72trueTb4b4b4, Te6edf3errorMessage Tff7b72= Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}

Te6edf3bleTransportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4}

T8b949e// ─── CancellationException is not silently swallowed ─────────────────────────────────────────

T8b949e/**
* [BleRadioTransport.close] cancels the [connectionScope]. The cancellation propagates as a [CancellationException]
* through the active coroutines in [discoverServicesAndSetupCharacteristics].
*
* Before the fix, `catch (e: Exception)` swallowed the [CancellationException] and the `disconnect()` call was
* skipped. After the fix, [disconnect] is called under [NonCancellable].
*
* This test uses a dedicated fake that throws [CancellationException] from [BleConnection.profile] to simulate the
* scope-cancellation path without races.
*/
Tf0883e@Test
Tff7b72fun Td2a8ff`disconnect is called when profile setup throws CancellationException`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3throwingConnection Tff7b72= Te6edf3CancellingProfileBleConnectionTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3throwingFactory Tff7b72=
Tff7b72object Tb4b4b4: T56d364BleConnectionFactory Tb4b4b4{
Tff7b72override Tff7b72fun Td2a8ffcreateTb4b4b4(Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4, Te6edf3tagTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3BleConnection Tff7b72= Te6edf3throwingConnection
Tb4b4b4}
Tff7b72val Te6edf3device Tff7b72= Te6edf3FakeBleDeviceTb4b4b4(Te6edf3address Tff7b72= Te6edf3addressTb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffTest RadioTa5d6ff"Tb4b4b4)
Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3bondTb4b4b4(Te6edf3deviceTb4b4b4)

Tff7b72val Te6edf3bleTransport Tff7b72=
Te6edf3BleRadioTransportTb4b4b4(
Te6edf3scope Tff7b72= Tff7b72thisTb4b4b4,
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3bluetoothRepository Tff7b72= Te6edf3bluetoothRepositoryTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3throwingFactoryTb4b4b4,
Te6edf3callback Tff7b72= Te6edf3serviceTb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Tb4b4b4)
Te6edf3bleTransportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)

T8b949e// Allow one connection attempt to reach profile() and be cancelled.
Te6edf3advanceTimeByTb4b4b4(T79c0ff4Te6edf3_000LTb4b4b4)

Te6edf3bleTransportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)

Te6edf3assertTrueTb4b4b4(
Te6edf3throwingConnectionTb4b4b4.Te6edf3disconnectCalls Tff7b72>Tff7b72= T79c0ff1Tb4b4b4,
Ta5d6ff"Ta5d6ffdisconnect() must be called after CancellationException in profile() β€” GATT leak fixTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

T8b949e// ─── Reconnect after a stable connection drops ───────────────────────────────────────────────

T8b949e/**
* Regression test for the BLE reconnect hang.
*
* Symptom: after a stable connection (uptime > minStableConnection) was terminated by a remote disconnect (e.g.
* node power-cycle), the transport's reconnect loop never iterated β€” `attemptConnection` ran exactly once, the GATT
* disconnect callback fired, and then nothing.
*
* Root cause: `attemptConnection` wrapped its disconnect-watcher in a `coroutineScope {
* connectionState.onEach{...}.launchIn(this); connectionState.first { Disconnected } }` block. `coroutineScope`
* waits for ALL launched children before returning, but the `.launchIn` collector on a hot `StateFlow` (or
* `SharedFlow(replay=1)`) never completes naturally. After `.first` returned, the scope hung forever, blocking
* `BleReconnectPolicy.execute` from issuing the next attempt.
*
* This test exercises the full happy-path reconnect cycle: connect β†’ stable uptime β†’ external disconnect β†’ expect a
* second `connectAndAwait` call. With the bug present, only one `connectAndAwait` call ever happens.
*/
Tf0883e@Test
Tff7b72fun Td2a8ff`transport reconnects after a stable connection is dropped remotely`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3device Tff7b72= Te6edf3FakeBleDeviceTb4b4b4(Te6edf3address Tff7b72= Te6edf3addressTb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffTest RadioTa5d6ff"Tb4b4b4)
Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3bondTb4b4b4(Te6edf3deviceTb4b4b4)

Tff7b72val Te6edf3bleTransport Tff7b72=
Te6edf3BleRadioTransportTb4b4b4(
Te6edf3scope Tff7b72= Tff7b72thisTb4b4b4,
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3bluetoothRepository Tff7b72= Te6edf3bluetoothRepositoryTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3connectionFactoryTb4b4b4,
Te6edf3callback Tff7b72= Te6edf3serviceTb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Tb4b4b4)
Te6edf3bleTransportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)

T8b949e// Settle delay (3 s) + connect + handshake.
Te6edf3advanceTimeByTb4b4b4(T79c0ff4Te6edf3_000LTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3connectionTb4b4b4.Te6edf3connectAndAwaitCalls Tff7b72=Tff7b72= T79c0ff1Tb4b4b4, Ta5d6ff"Ta5d6ffFirst connect must happen during initial start windowTa5d6ff"Tb4b4b4)

T8b949e// Stay connected long enough to be considered stable (> minStableConnection = 5 s).
Te6edf3advanceTimeByTb4b4b4(T79c0ff1T79c0ff0Te6edf3_000LTb4b4b4)

T8b949e// Simulate the firmware dying mid-session β€” the same path a node power-cycle takes.
Te6edf3connectionTb4b4b4.Te6edf3simulateRemoteDisconnectTb4b4b4(Te6edf3reason Tff7b72= Te6edf3DisconnectReasonTb4b4b4.Te6edf3TimeoutTb4b4b4)

T8b949e// Settle delay (3 s) before the next attempt + re-connect window. Generous to absorb
T8b949e// the policy retry backoff (5 s on first failure) plus another 3 s settle delay.
Te6edf3advanceTimeByTb4b4b4(T79c0ff3T79c0ff0Te6edf3_000LTb4b4b4)

Te6edf3assertTrueTb4b4b4(
Te6edf3connectionTb4b4b4.Te6edf3connectAndAwaitCalls Tff7b72>Tff7b72= T79c0ff2Tb4b4b4,
Ta5d6ff"Ta5d6ffReconnect loop must call connectAndAwait again after a remote disconnect Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff(actual calls: Tffd700${Te6edf3connectionTb4b4b4.Te6edf3connectAndAwaitCallsTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4,
Tb4b4b4)

Te6edf3bleTransportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// ─── Test doubles ────────────────────────────────────────────────────────────────────────────────

T8b949e/**
* A [BleConnection] that succeeds at [connectAndAwait] but throws [CancellationException] from [profile]. This
* simulates what happens when the owning coroutine scope is cancelled while GATT service discovery is in progress.
*/
Tff7b72private Tff7b72class T56d364CancellingProfileBleConnection Tb4b4b4: Te6edf3BleConnection Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3_deviceFlow Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3BleDevice?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72override Tff7b72val Te6edf3deviceFlowTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3BleDevice?Tff7b72> Tff7b72= Te6edf3_deviceFlowTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

Tff7b72private Tff7b72val Te6edf3_connectionState Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3BleConnectionStateTff7b72>Tb4b4b4(Te6edf3BleConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72override Tff7b72val Te6edf3connectionStateTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3BleConnectionStateTff7b72> Tff7b72= Te6edf3_connectionStateTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72val Te6edf3deviceTb4b4b4: Te6edf3BleDevice? Tff7b72= Tff7b72null

Tff7b72var Te6edf3disconnectCalls Tff7b72= T79c0ff0

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffconnectTb4b4b4(Te6edf3deviceTb4b4b4: Te6edf3BleDeviceTb4b4b4) Tb4b4b4{
Te6edf3_deviceFlowTb4b4b4.Te6edf3value Tff7b72= Te6edf3device
Te6edf3_connectionStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3BleConnectionStateTb4b4b4.Te6edf3Connected
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffconnectAndAwaitTb4b4b4(Te6edf3deviceTb4b4b4: Te6edf3BleDeviceTb4b4b4, Te6edf3timeoutTb4b4b4: Te6edf3DurationTb4b4b4)Tb4b4b4: Te6edf3BleConnectionState Tb4b4b4{
Te6edf3connectTb4b4b4(Te6edf3deviceTb4b4b4)
Tff7b72return Te6edf3BleConnectionStateTb4b4b4.Te6edf3Connected
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffdisconnectTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3disconnectCallsTff7b72+Tff7b72+
Te6edf3_connectionStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3BleConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4(Tb4b4b4)
Te6edf3_deviceFlowTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffprofileTb4b4b4(
Te6edf3serviceUuidTb4b4b4: Te6edf3kotlinTb4b4b4.Te6edf3uuidTb4b4b4.Te6edf3UuidTb4b4b4,
Te6edf3timeoutTb4b4b4: Te6edf3DurationTb4b4b4,
Te6edf3setupTb4b4b4: Te6edf3suspend Te6edf3CoroutineScopeTb4b4b4.Tb4b4b4(Te6edf3BleServiceTb4b4b4) Tff7b72-Tff7b72> Te6edf3TTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3T Tff7b72= Tff7b72throw Te6edf3CancellationExceptionTb4b4b4(Ta5d6ff"Ta5d6ffSimulated scope cancellation during service discoveryTa5d6ff"Tb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffmaximumWriteValueLengthTb4b4b4(Te6edf3writeTypeTb4b4b4: Te6edf3BleWriteTypeTb4b4b4)Tb4b4b4: Tffa657Int? Tff7b72= Tff7b72null
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.11s